use std::io;

use agent::generic::{create_agent, ProviderConfig};
use common::compose::{CallBox, Composer, make_empty_ctx};
use htmd::HtmlToMarkdown;
use log;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum MarkdownError {
    #[error("HTML to Markdown conversion error: {0}")]
    IoError(#[from] io::Error),

    #[error("Provider error: {0}")]
    ProviderError(String),

    #[error("Execution error: {0}")]
    ExecutionError(#[from] common::compose::FError),
}

/// 使用 htmd 库将 HTML 转换为 Markdown
pub fn common_convert(html: &str) -> Result<String, MarkdownError> {
    let converter = HtmlToMarkdown::builder()
        .skip_tags(vec!["script", "style", "head"])
        .build();

    Ok(converter.convert(html)?)
}

/// 创建一个 CallBox，使用 AI 模型将 HTML 转换为 Markdown
///
/// 使用 AI 模型将 HTML 转换为 Markdown，相比于简单的库转换，
/// 这种方法可以更好地理解 HTML 的语义，生成更高质量的 Markdown。
///
/// # 参数
///
/// * `provider_config` - AI 提供商配置
/// * `model` - 模型名称，如果为空则使用默认模型
///
/// # 示例
///
/// ```rust,no_run
/// use markdown::create_agent_html_to_markdown;
/// use agent::generic::ProviderConfig;
/// use common::compose::make_empty_ctx;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let provider_config = ProviderConfig::OpenAI {
///         api_key: "your-api-key".to_string(),
///         base_url: None,
///         temperature: 0.3,
///         max_tokens: None,
///     };
///     let converter = create_agent_html_to_markdown(provider_config, "");
///     let html = "<h1>Hello World</h1><p>This is a <b>test</b>.</p>";
///     let markdown = converter.run(html.to_string(), make_empty_ctx()).await?;
///     println!("{}", markdown);
///     Ok(())
/// }
/// ```
pub fn create_agent_html_to_markdown(
    provider_config: ProviderConfig,
    model: &str,
) -> CallBox<'static, String, String> {
    let system_prompt = r#"
You are an HTML to Markdown converter. Your task is to convert HTML code into clean, well-formatted Markdown.

**Important Note:** This HTML might contain non-standard or non-semantic tag usage. For example:
*   Code blocks might not use `<pre>` or `<code>` tags, but instead use `<div>` or other generic tags.
*   Bold text might use `<b>` tags, or be implemented via CSS classes (e.g., `<span class="bold">`), rather than the standard `<strong>`.
*   Headings might not use `<h1>`-`<h6>` tags, but instead use `<div>` or `<p>` tags with specific styling (e.g., larger font size, bold).
*   Lists might not use `<ul>`/`<ol>` and `<li>`, but instead use `<p>` tags preceded by bullet points or numbers.

**Your task is to:**
1.  Carefully analyze the HTML structure, content, and potential styling clues (if any).
2.  **Based on context and inferred semantics**, convert the HTML elements into the most appropriate Markdown syntax.
    *   **Example (Code Block):** If a `<div>` or `<p>` contains multiple lines of text, features a monospace font, or is clearly a code snippet, convert it into a Markdown code block (```).
    *   **Example (Bold):** If text is wrapped in `<b>` tags, or its styling indicates it's bold, use Markdown's bold syntax (`**text**`).
    *   **Example (Heading):** If the text within a `<div>` or `<p>` looks like a heading (e.g., larger font, bold, on its own line, at the start of a content section), infer its visual hierarchy and use the corresponding Markdown heading syntax (`# H1`, `## H2`, etc.).
    *   **Example (List):** If multiple consecutive `<p>` tags start with bullet points (like `-`, `*`) or numbers (like `1.`, `2.`), convert them into a Markdown list.
3.  Ensure the resulting Markdown accurately reflects the structure and formatting intent of the original content.
4.  Prioritize handling common Markdown elements: headings, paragraphs, lists (ordered and unordered), code blocks (inline and block-level), bold, italics, links, images, etc.

**CRITICAL INSTRUCTIONS:**
- ONLY output the converted Markdown content.
- DO NOT add any explanations, comments, or additional text.
- DO NOT include any statements about your training data or capabilities.
- DO NOT include phrases like "You are trained on data up to..." or similar.
- DO NOT include any text that wasn't in the original HTML.

Please process the following HTML:
"#;

    // 创建基本的 AI 代理
    let base_agent = create_agent(
        provider_config,
        model,
        system_prompt
    );

    // 包装代理，处理特殊返回值
    Composer::from_fn(move |html: String| {
        let agent = base_agent.clone();

        async move {
            // 如果 HTML 为空，直接返回空字符串
            if html.trim().is_empty() {
                return Ok("".to_string());
            }

            // 调用 AI 代理进行转换
            let result = agent.run(html, make_empty_ctx()).await?;

            // 处理特殊返回值
            if result.trim() == "[EMPTY_CONTENT]" {
                log::debug!("AI 返回了空内容标记，转换为空字符串");
                Ok("".to_string())
            } else {
                Ok(result)
            }
        }
    })
}

/// 创建一个 CallBox，使用 htmd 库将 HTML 转换为 Markdown
///
/// 使用 htmd 库将 HTML 转换为 Markdown，这是一种简单高效的转换方式，
/// 适用于标准的 HTML 文档。
///
/// # 示例
///
/// ```rust,no_run
/// use markdown::create_lib_html_to_markdown;
/// use common::compose::make_empty_ctx;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let converter = create_lib_html_to_markdown();
///     let html = "<h1>Hello World</h1><p>This is a <b>test</b>.</p>";
///     let markdown = converter.run(html.to_string(), make_empty_ctx()).await?;
///     println!("{}", markdown);
///     Ok(())
/// }
/// ```
pub fn create_lib_html_to_markdown() -> CallBox<'static, String, String> {
    Composer::from_fn(|html: String| {
        async move {
            // 如果 HTML 为空，直接返回空字符串
            if html.trim().is_empty() {
                return Ok("".to_string());
            }

            // 使用 common_convert 进行转换
            match common_convert(&html) {
                Ok(markdown) => Ok(markdown),
                Err(e) => {
                    log::error!("HTML 转换失败: {}", e);
                    Err(common::compose::FError::Generic(Box::new(e)))
                }
            }
        }
    })
}

/// 创建一个 CallBox，用于将 HTML 转换为 Markdown
///
/// 根据提供的参数，选择使用 AI 模型或库转换的方式。
/// 当提供 agent_config 时，使用 AI 模型进行转换；
/// 当 agent_config 为 None 时，使用库转换。
///
/// # 参数
///
/// * `agent_config` - 可选的 AI 代理配置，如果为 None，则使用库转换
///                  当提供时，包含 AI 提供商配置和模型名称
///
/// # 示例
///
/// ```rust,no_run
/// use markdown::create_html_to_markdown;
/// use agent::generic::ProviderConfig;
/// use common::compose::make_empty_ctx;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     // 使用 AI 模型转换
///     let provider_config = ProviderConfig::OpenAI {
///         api_key: "your-api-key".to_string(),
///         base_url: None,
///         temperature: 0.3,
///         max_tokens: None,
///     };
///     let agent_config = Some((provider_config, "gpt-4o"));
///     let converter = create_html_to_markdown(agent_config);
///     let html = "<h1>Hello World</h1><p>This is a <b>test</b>.</p>";
///     let markdown = converter.run(html.to_string(), make_empty_ctx()).await?;
///     println!("{}", markdown);
///
///     // 使用库转换
///     let converter = create_html_to_markdown(None);
///     let markdown = converter.run(html.to_string(), make_empty_ctx()).await?;
///     println!("{}", markdown);
///     Ok(())
/// }
/// ```
pub fn create_html_to_markdown(
    agent_config: Option<(ProviderConfig, &str)>,
) -> CallBox<'static, String, String> {
    match agent_config {
        Some((config, model)) => create_agent_html_to_markdown(config, model),
        None => create_lib_html_to_markdown(),
    }
}

/// 异步将 HTML 转换为 Markdown
///
/// 这是一个便捷函数，它创建一个转换器并使用它将 HTML 转换为 Markdown。
///
/// # 参数
///
/// * `html` - 要转换的 HTML 字符串
/// * `agent_config` - 可选的 AI 代理配置，如果为 None，则使用库转换
pub async fn html_to_markdown(
    html: &str,
    agent_config: Option<(ProviderConfig, &str)>,
) -> Result<String, MarkdownError> {
    let converter = create_html_to_markdown(agent_config);
    converter.run(html.to_string(), make_empty_ctx()).await.map_err(MarkdownError::ExecutionError)
}

#[cfg(test)]
mod tests {
    use super::*;
    use dotenvy::dotenv;

    #[test]
    fn test_convert() -> Result<(), MarkdownError> {
        let html = "<h1>Hello World</h1><p>This is a <b>test</b>.</p>";
        let markdown = common_convert(html)?;
        assert!(markdown.contains("# Hello World"));
        assert!(markdown.contains("This is a **test**."));
        Ok(())
    }

    #[tokio::test]
    async fn test_html_to_markdown() {
        dotenv().ok();

        // 如果没有设置环境变量，则跳过测试
        if std::env::var("OPENAI_API_KEY").is_err() {
            println!("跳过测试：未设置 OPENAI_API_KEY 环境变量");
            return;
        }

        let api_key = std::env::var("OPENAI_API_KEY").unwrap();
        let base_url = std::env::var("OPENAI_BASE_URL").unwrap();

        let html = "<div class='header'><h1>Hello World</h1></div><p>This is a <b>test</b>.</p><ul><li>Item 1</li><li>Item 2</li></ul>";

        let provider_config = ProviderConfig::OpenAI {
            api_key,
            base_url: Some(base_url),
            temperature: 0.2,
            max_tokens: Some(500),
        };

        match html_to_markdown(html, Some((provider_config, ""))).await {
            Ok(markdown) => {
                println!("转换结果:\n{}", markdown);
                assert!(markdown.contains("Hello World"));
                assert!(markdown.contains("test"));
                assert!(markdown.contains("Item 1"));
                assert!(markdown.contains("Item 2"));
            },
            Err(e) => {
                println!("测试失败: {}", e);
                assert!(false, "测试失败: {}", e);
            }
        }
    }

    #[tokio::test]
    async fn test_html_to_markdown2() {
        dotenv().ok();

        // 如果没有设置环境变量，则跳过测试
        if std::env::var("OPENAI_API_KEY").is_err() {
            println!("跳过测试：未设置 OPENAI_API_KEY 环境变量");
            return;
        }

        let api_key = std::env::var("OPENAI_API_KEY").unwrap();
        let base_url = std::env::var("OPENAI_BASE_URL").unwrap();

        let html = std::fs::read_to_string("./output/chunks/chunk_4.html").expect("Failed to read file");

        let provider_config = ProviderConfig::OpenAI {
            api_key,
            base_url: Some(base_url),
            temperature: 0.2,
            max_tokens: Some(500),
        };

        match html_to_markdown(&html, Some((provider_config, "gpt-4o-mini"))).await {
            Ok(markdown) => {
                println!("转换结果:\n{}", markdown);
            },
            Err(e) => {
                println!("测试失败: {}", e);
                assert!(false, "测试失败: {}", e);
            }
        }
    }

    #[tokio::test]
    async fn test_lib_html_to_markdown() {
        let html = "<div class='header'><h1>Hello World</h1></div><p>This is a <b>test</b>.</p><ul><li>Item 1</li><li>Item 2</li></ul>";

        // 使用库转换
        match html_to_markdown(html, None).await {
            Ok(markdown) => {
                println!("库转换结果:\n{}", markdown);
                assert!(markdown.contains("Hello World"));
                assert!(markdown.contains("test"));
                assert!(markdown.contains("Item 1"));
                assert!(markdown.contains("Item 2"));
            },
            Err(e) => {
                println!("测试失败: {}", e);
                assert!(false, "测试失败: {}", e);
            }
        }
    }
}
